/* 
状态管理的三个核心要素：
1. 状态，组件之外可共享
  - 闭包
2. 明确修改状态的方法，并可以通知到指定位置
  - 发布订阅
  - proxy
3. 状态数据修改后，可触发 UI 的重新渲染
  - forceUpdate（setState(Math.random())）
  - react useSyncExternalStore
*/

import { useEffect, useState, useRef } from "react";
import useSyncExternalStoreExport from 'use-sync-external-store/shim/with-selector';
import isEqual from 'fast-deep-equal';

const { useSyncExternalStoreWithSelector } = useSyncExternalStoreExport;

function createStore(createState) {
  let state;
  let listeners = new Set();

  function getState() {
    return state;
  }

  function setState(partial, isReplace) {
    const partialState = typeof partial === 'function' ? partial(state) : partial;
    const prevState = state;

    if (Object.is(partialState, state)) return;

    if (!isReplace) {
      state = { ...state, ...partialState };
    } else {
      state = partialState;
    }

    listeners.forEach(listener => listener(state, prevState));
  }

  function subscribe(listener) {
    listeners.add(listener);

    return () => listeners.delete(listener);
  }

  function destory() {
    listeners.clear();
  }

  const api = { getState, setState, subscribe, destory};

  state = createState(setState, getState, api);

  return api;
}

/* function useStore(selector, api) {
  const [,setRandom] = useState();
  const prevState = useRef();

  useEffect(() => {
    api.subscribe((state, prevState) => {
      const nextObj = selector(state);
      const prevObj = selector(prevState)

      const isEqual = Object.is(nextObj, prevObj)
      if (!isEqual) {
        setRandom(Math.random());
      }

    });
  }, []);

  return selector(api.getState());
} */

function useStore(selector, api) {

  const _isEqual = (a, b) => Object.is(a, b);

  return useSyncExternalStoreWithSelector(
    api.subscribe,
    api.getState,
    api.getState,
    selector,
    _isEqual
  );
}

function create(createState) {
  const api = createStore(createState);
  const useBoundStore = selector => useStore(selector, api);

  Object.assign(useBoundStore, api);

  return useBoundStore;
}

function useShallow(selector) {
  const prev = useRef();
  
  return state => {
    const next = selector(state);
    return isEqual(next, prev.current) ? prev.current : (prev.current = next)
  };
}

function logMiddleware(fn) {
  return function(set, get, api) {
    function newSet(partial) {
      console.log('prevState ---> ', get());
      console.log('partial ---> ', partial);
      return set(partial);
    }

    return fn(newSet, get, api)
  }
}

// -----------

const useXxxStore = create(logMiddleware((set, get, store) => ({
  aaa: '',
  bbb: { b: 1 },
  updateAaa: (value) => set({ aaa: value }),
  updateBbb: (value) => set({ bbb: value })
})));

console.log('getState ---> ', useXxxStore.getState());

export default function App() {
  const store = useXxxStore(useShallow(state => ({
    aaa: state.aaa,
    bbb: state.bbb,
    updateAaa: state.updateAaa,
    updateBbb: state.updateBbb
  })));

  const onChange = (e) => {
    store.updateAaa(e.target.value);
  };

  console.log('render');

  return <div>
    <input onChange={onChange}></input>
    {store.aaa}
    <hr />
    {JSON.stringify(store.bbb)}
    <button onClick={() => {
      store.updateBbb({ b: 1 });
    }}>change</button>
  </div>
}
